


/*-------------------------------------------------------------------------
 *
 * generation.c
 *	  代际分配器定义。
 *
 * 代际是一个自定义的 MemoryContext 实现，旨在处理生命周期相似的块。
 *
 * Portions Copyright (c) 2017-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/mmgr/generation.c
 *
 *
 *	这个内存上下文的设计基于假设：块的释放顺序大致与分配顺序相同（FIFO），
 *	或者在具有相似生命周期的组内（代 - 这也是该上下文名称的来源）。这对于各种
 *	类似队列的用例是典型的，比如在构建、处理元组然后丢弃的情况下。
 *
 *	内存上下文使用一种非常简单的方式管理空闲空间。每个块跟踪扬言和被释放的块数量。
 *	当空闲块的数量等于已分配块的数量时，块被视为空块。当这种情况发生时，
 *	我们尝试“回收”该块，即将其重新用于新的分配，而不是释放该块。通过将该块设置为
 *	上下文的 'freeblock' 字段来实现。如果 freeblock 字段已经被另一个空闲块占用，
 *	我们简单地将新空块返回给 malloc。
 *
 *	这种处理空闲块的方法要求对于真正的首次分配，首次释放的分配模式需要较少的
 *	malloc/free 调用。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "lib/ilist.h"
#include "port/pg_bitutils.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"


#define Generation_BLOCKHDRSZ	MAXALIGN(sizeof(GenerationBlock))
#define Generation_CHUNKHDRSZ	sizeof(GenerationChunk)

#define Generation_CHUNK_FRACTION	8

typedef struct GenerationBlock GenerationBlock; /* 前向引用 */
typedef struct GenerationChunk GenerationChunk;

typedef void *GenerationPointer;

/*
 * GenerationContext是一个简单的内存上下文，不重用已分配的块，
 * 并在所有块释放后释放块。
 */
typedef struct GenerationContext
{
	MemoryContextData header;	/* 标准内存上下文字段 */

	/* 代际上下文参数 */
	Size		initBlockSize;	/* 初始块大小 */
	Size		maxBlockSize;	/* 最大块大小 */
	Size		nextBlockSize;	/* 下一个要分配的块大小 */
	Size		allocChunkLimit;	/* 有效的块大小限制 */

	GenerationBlock *block;		/* 当前（最近分配的）块，或如果我们刚刚释放了最近的
								 * 块则为NULL */
	GenerationBlock *freeblock; /* 指向正在回收的块的指针，
								 * 如果没有这样的块则为NULL。 */
	GenerationBlock *keeper;	/* 在重置时保留此块 */
	dlist_head	blocks;			/* 块的列表 */
} GenerationContext;

/*
 * GenerationBlock
 *		GenerationBlock是通过generation.c从malloc()获得的内存单元。
 *		它包含零个或多个GenerationChunks，这些是通过palloc()请求且通过
 *		pfree()释放的单元。GenerationChunks不能单独返回给malloc()，
 *		相反，pfree()会更新块的空闲计数，当块中的所有块都空闲时，
 *		整个块可以返回给malloc()。
 *
 *		GenerationBlock是块的头部数据 --- 块内的可用空间
 *		从下一个对齐边界开始。
 */
struct GenerationBlock
{
	dlist_node	node;			/* 双向链表块 */
	Size		blksize;		/* 该块的分配大小 */
	int			nchunks;		/* 块中的块数 */
	int			nfree;			/* 空闲块的数量 */
	char	   *freeptr;		/* 此块中空闲空间的起始位置 */
	char	   *endptr;			/* 此块中空间的结束位置 */
};

/*
 * GenerationChunk
 *		GenerationBlock中每个内存块的前缀
 *
 * 注意：为了满足内存上下文API，块的有效载荷区域必须是最大对齐的，
 * 并且“上下文”链接必须紧邻有效载荷区域（参见GetMemoryChunkContext）。
 * 我们通过要求sizeof(GenerationChunk)为最大对齐来简化此模块，
 * 然后我们可以通过在指针字段之前添加任何所需的对齐填充来确保
 * 一切正常。下面有一个静态断言，确保对齐是正确的。
 */
struct GenerationChunk
{
	/* 大小始终是块内可用空间的大小 */
	Size		size;
#ifdef MEMORY_CONTEXT_CHECKING
	/* 当调试内存使用时，还存储实际请求的大小 */
	/* 这是空闲块中的零 */
	Size		requested_size;

#define GENERATIONCHUNK_RAWSIZE  (SIZEOF_SIZE_T * 2 + SIZEOF_VOID_P * 2)
#else
#define GENERATIONCHUNK_RAWSIZE  (SIZEOF_SIZE_T + SIZEOF_VOID_P * 2)
#endif							/* MEMORY_CONTEXT_CHECKING */

	/* 如果需要，通过添加填充来确保正确对齐 */
#if (GENERATIONCHUNK_RAWSIZE % MAXIMUM_ALIGNOF) != 0
	char		padding[MAXIMUM_ALIGNOF - GENERATIONCHUNK_RAWSIZE % MAXIMUM_ALIGNOF];
#endif

	GenerationBlock *block;		/* 拥有此块的块 */
	GenerationContext *context; /* 拥有上下文，或如果是已释放的块则为 NULL */
	/* 此处不得有填充以达到 MAXALIGN 边界！ */
};

/*
 * 只有“context”字段应该在此模块外部访问。
 * 在使用valgrind时，我们将保留分配块头部标记为NOACCESS。但请注意，已释放的块头部保持可访问，出于简便考虑。
 */
#define GENERATIONCHUNK_PRIVATE_LEN	offsetof(GenerationChunk, context)

/*
 * GenerationIsValid
 *		当且仅当集合是有效的分配集合时返回真。
 */
#define GenerationIsValid(set) PointerIsValid(set)

#define GenerationPointerGetChunk(ptr) \
	((GenerationChunk *)(((char *)(ptr)) - Generation_CHUNKHDRSZ))
#define GenerationChunkGetPointer(chk) \
	((GenerationPointer *)(((char *)(chk)) + Generation_CHUNKHDRSZ))

/* 内联助手函数 */
static inline void fc_GenerationBlockInit(GenerationBlock *fc_block, Size fc_blksize);
static inline bool fc_GenerationBlockIsEmpty(GenerationBlock *fc_block);
static inline void fc_GenerationBlockMarkEmpty(GenerationBlock *fc_block);
static inline Size fc_GenerationBlockFreeBytes(GenerationBlock *fc_block);
static inline void fc_GenerationBlockFree(GenerationContext *fc_set,
									   GenerationBlock *fc_block);

/*
 * 这些函数实现了Generation上下文的MemoryContext API。
 */
static void *fc_GenerationAlloc(MemoryContext fc_context, Size fc_size);
static void fc_GenerationFree(MemoryContext fc_context, void *fc_pointer);
static void *fc_GenerationRealloc(MemoryContext fc_context, void *fc_pointer, Size fc_size);
static void fc_GenerationReset(MemoryContext fc_context);
static void fc_GenerationDelete(MemoryContext fc_context);
static Size fc_GenerationGetChunkSpace(MemoryContext fc_context, void *fc_pointer);
static bool fc_GenerationIsEmpty(MemoryContext fc_context);
static void fc_GenerationStats(MemoryContext fc_context,
							MemoryStatsPrintFunc fc_printfunc, void *fc_passthru,
							MemoryContextCounters *fc_totals,
							bool fc_print_to_stderr);

#ifdef MEMORY_CONTEXT_CHECKING
static void fc_GenerationCheck(MemoryContext fc_context);
#endif

/*
 * 这是Generation上下文的虚拟函数表。
 */
static const MemoryContextMethods GenerationMethods = {
	fc_GenerationAlloc,
	fc_GenerationFree,
	fc_GenerationRealloc,
	fc_GenerationReset,
	fc_GenerationDelete,
	fc_GenerationGetChunkSpace,
	fc_GenerationIsEmpty,
	fc_GenerationStats
#ifdef MEMORY_CONTEXT_CHECKING
	,fc_GenerationCheck
#endif
};


/*
 * 公共例程
 */


/*
 * GenerationContextCreate
 *		创建一个新的Generation上下文。
 *
 * parent: 父上下文，如果是顶级上下文则为NULL
 * name: 上下文名称（必须是静态分配）
 * minContextSize: 最小上下文大小
 * initBlockSize: 初始分配块大小
 * maxBlockSize: 最大分配块大小
 */
MemoryContext GenerationContextCreate(MemoryContext fc_parent,
						const char *fc_name,
						Size fc_minContextSize,
						Size fc_initBlockSize,
						Size fc_maxBlockSize)
{
	Size		fc_firstBlockSize;
	Size		fc_allocSize;
	GenerationContext *fc_set;
	GenerationBlock *fc_block;

	/* 断言我们已经适当填充了GenerationChunk */
	StaticAssertStmt(Generation_CHUNKHDRSZ == MAXALIGN(Generation_CHUNKHDRSZ),
					 "sizeof(GenerationChunk) is not maxaligned");
	StaticAssertStmt(offsetof(GenerationChunk, context) + sizeof(MemoryContext) ==
					 Generation_CHUNKHDRSZ,
					 "padding calculation in GenerationChunk is wrong");

	/*
	 * 首先，验证分配参数。断言似乎足够，因为
	 * 没有人在运行时改变他们的参数。我们有点随意
	 * 强制执行最低1K块大小。
	 */
	Assert(fc_initBlockSize == MAXALIGN(fc_initBlockSize) &&
		   fc_initBlockSize >= 1024);
	Assert(fc_maxBlockSize == MAXALIGN(fc_maxBlockSize) &&
		   fc_maxBlockSize >= fc_initBlockSize &&
		   AllocHugeSizeIsValid(fc_maxBlockSize)); /* 必须能够安全地加倍 */
	Assert(fc_minContextSize == 0 ||
		   (fc_minContextSize == MAXALIGN(fc_minContextSize) &&
			fc_minContextSize >= 1024 &&
			fc_minContextSize <= fc_maxBlockSize));

	/* 确定初始块的大小 */
	fc_allocSize = MAXALIGN(sizeof(GenerationContext)) +
		Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ;
	if (fc_minContextSize != 0)
		fc_allocSize = Max(fc_allocSize, fc_minContextSize);
	else
		fc_allocSize = Max(fc_allocSize, fc_initBlockSize);

	/*
	 * 分配初始块。与其他generation.c块不同，它
	 * 以上下文头开始，其块头跟随在后。
	 */
	fc_set = (GenerationContext *) malloc(fc_allocSize);
	if (fc_set == NULL)
	{
		MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed while creating memory context \"%s\".",
						   fc_name)));
	}

	/*
	 * 避免编写可能在这里和MemoryContextCreate之间失败的代码；
	 * 如果我们在这个过程中报告错误，将导致标题泄漏。
	 */
	dlist_init(&fc_set->blocks);

	/* 填充初始块的块头 */
	fc_block = (GenerationBlock *) (((char *) fc_set) + MAXALIGN(sizeof(GenerationContext)));
	/* 确定块大小并初始化它 */
	fc_firstBlockSize = fc_allocSize - MAXALIGN(sizeof(GenerationContext));
	fc_GenerationBlockInit(fc_block, fc_firstBlockSize);

	/* 将其添加到双向链表块中 */
	dlist_push_head(&fc_set->blocks, &fc_block->node);

	/* 使用它作为当前分配块 */
	fc_set->block = fc_block;

	/* 还没有空闲块 */
	fc_set->freeblock = NULL;

	/* 将块标记为在重置时不应释放 */
	fc_set->keeper = fc_block;

	/* 填充GenerationContext特定的头字段 */
	fc_set->initBlockSize = fc_initBlockSize;
	fc_set->maxBlockSize = fc_maxBlockSize;
	fc_set->nextBlockSize = fc_initBlockSize;

	/*
	 * 计算此上下文的分配块大小限制。
	 *
	 * 遵循与AllocSet类似的思想，详情见aset.c ...
	 */
	fc_set->allocChunkLimit = fc_maxBlockSize;
	while ((Size) (fc_set->allocChunkLimit + Generation_CHUNKHDRSZ) >
		   (Size) ((Size) (fc_maxBlockSize - Generation_BLOCKHDRSZ) / Generation_CHUNK_FRACTION))
		fc_set->allocChunkLimit >>= 1;

	/* 最后，执行上下文创建的类型无关部分 */
	MemoryContextCreate((MemoryContext) fc_set,
						T_GenerationContext,
						&GenerationMethods,
						fc_parent,
						fc_name);

	((MemoryContext) fc_set)->mem_allocated = fc_firstBlockSize;

	return (MemoryContext) fc_set;
}

/*
 * GenerationReset
 *		释放在给定集合中分配的所有内存。
 *
 * 代码简单地释放上下文中的所有块 - 我们不保留任何
 * 保持块或类似的东西。
 */
static void fc_GenerationReset(MemoryContext fc_context)
{
	GenerationContext *fc_set = (GenerationContext *) fc_context;
	dlist_mutable_iter fc_miter;

	AssertArg(GenerationIsValid(fc_set));

#ifdef MEMORY_CONTEXT_CHECKING
	/* 在释放前检查损坏和泄漏 */
	fc_GenerationCheck(fc_context);
#endif

	/*
	 * 将空闲块指针置为NULL。我们必须在调用
	 * GenerationBlockFree之前执行此操作，因为该函数
	 * 从不期望释放空闲块。
	 */
	fc_set->freeblock = NULL;

	dlist_foreach_modify(fc_miter, &fc_set->blocks)
	{
		GenerationBlock *fc_block = dlist_container(GenerationBlock, node, fc_miter.cur);

		if (fc_block == fc_set->keeper)
			fc_GenerationBlockMarkEmpty(fc_block);
		else
			fc_GenerationBlockFree(fc_set, fc_block);
	}

	/* 将其设置为使新的分配使用保留块 */
	fc_set->block = fc_set->keeper;

	/* 也重置块大小分配序列 */
	fc_set->nextBlockSize = fc_set->initBlockSize;

	/* 确保双向链表中只有1个项 */
	Assert(!dlist_is_empty(&fc_set->blocks));
	Assert(!dlist_has_next(&fc_set->blocks, dlist_head_node(&fc_set->blocks)));
}

/*
 * GenerationDelete
 *		释放在给定上下文中分配的所有内存。
 */
static void fc_GenerationDelete(MemoryContext fc_context)
{
	/* 重置以释放所有可释放的GenerationBlocks */
	fc_GenerationReset(fc_context);
	/* 并释放上下文头和保留块 */
	free(fc_context);
}

/*
 * GenerationAlloc
 *		返回指向给定大小分配内存的指针，如果
 *		请求无法完成，则返回NULL；内存被添加到集合中。
 *
 * 任何请求不得超过：
 *		MAXALIGN_DOWN(SIZE_MAX) - Generation_BLOCKHDRSZ - Generation_CHUNKHDRSZ
 * 所有调用者使用的限制要低得多。
 *
 * 注意：使用valgrind时，返回的分配如何标记无关紧要，
 * 因为mcxt.c将其设置为UNDEFINED。在某些路径中，我们将
 * 返回标记为NOACCESS的空间 - GenerationRealloc必须小心！
 */
static void * fc_GenerationAlloc(MemoryContext fc_context, Size fc_size)
{
	GenerationContext *fc_set = (GenerationContext *) fc_context;
	GenerationBlock *fc_block;
	GenerationChunk *fc_chunk;
	Size		fc_chunk_size = MAXALIGN(fc_size);
	Size		fc_required_size = fc_chunk_size + Generation_CHUNKHDRSZ;

	/* 是否是过大的块？如果是，则分配特殊块 */
	if (fc_chunk_size > fc_set->allocChunkLimit)
	{
		Size		fc_blksize = fc_required_size + Generation_BLOCKHDRSZ;

		fc_block = (GenerationBlock *) malloc(fc_blksize);
		if (fc_block == NULL)
			return NULL;

		fc_context->mem_allocated += fc_blksize;

		/* 带有单个（已使用）块的块 */
		fc_block->blksize = fc_blksize;
		fc_block->nchunks = 1;
		fc_block->nfree = 0;

		/* 块完全满 */
		fc_block->freeptr = fc_block->endptr = ((char *) fc_block) + fc_blksize;

		fc_chunk = (GenerationChunk *) (((char *) fc_block) + Generation_BLOCKHDRSZ);
		fc_chunk->block = fc_block;
		fc_chunk->context = fc_set;
		fc_chunk->size = fc_chunk_size;

#ifdef MEMORY_CONTEXT_CHECKING
		fc_chunk->requested_size = fc_size;
		/* 设置标记以捕捉“未使用”空间的覆盖 */
		if (fc_size < fc_chunk_size)
			set_sentinel(GenerationChunkGetPointer(fc_chunk), fc_size);
#endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY
		/* 用垃圾填充分配的空间 */
		randomize_mem((char *) GenerationChunkGetPointer(fc_chunk), fc_size);
#endif

		/* 将块添加到已分配块的列表中 */
		dlist_push_head(&fc_set->blocks, &fc_block->node);

		/* 确保任何填充字节标记为NOACCESS。 */
		VALGRIND_MAKE_MEM_NOACCESS((char *) GenerationChunkGetPointer(fc_chunk) + fc_size,
								   chunk_size - fc_size);

		/* 禁止外部访问块头的私有部分。 */
		VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);

		return GenerationChunkGetPointer(fc_chunk);
	}

	/*
	 * 不是过大的块。我们首先尝试利用当前块，
	 * 但如果其中没有足够的空间，则不分配新的
	 * 块，而是查看空闲块是否为空且有足够的空间。
	 * 如果没有，我们还会尝试使用保留块。保留
	 * 块可能已经变为空，如果我们不显式尝试
	 * 在这里使用它，就没有其他方法可以重新利用它。
	 *
	 * 我们不想在当前块未满之前开始填充空闲块，
	 * 否则可能会导致FIFO类型工作负载中的碎片化。
	 * 只有在那些块完全为空时，我们才切换到使用空闲块或保留块。
	 * 如果不这样做，我们可能会结束在多个块中碎片化
	 * 连续分配，这会随着时间的推移而加重问题。
	 */
	fc_block = fc_set->block;

	if (fc_block == NULL ||
		fc_GenerationBlockFreeBytes(fc_block) < fc_required_size)
	{
		Size		fc_blksize;
		GenerationBlock *fc_freeblock = fc_set->freeblock;

		if (fc_freeblock != NULL &&
			fc_GenerationBlockIsEmpty(fc_freeblock) &&
			fc_GenerationBlockFreeBytes(fc_freeblock) >= fc_required_size)
		{
			fc_block = fc_freeblock;

			/*
			 * 将空闲块清零，因为我们将在下面将其设置为当前块
			 */
			fc_set->freeblock = NULL;
		}
		else if (fc_GenerationBlockIsEmpty(fc_set->keeper) &&
				 fc_GenerationBlockFreeBytes(fc_set->keeper) >= fc_required_size)
		{
			fc_block = fc_set->keeper;
		}
		else
		{
			/*
			 * 第一个这样的块大小为 initBlockSize，我们在每个后续块中
			 * 使空间加倍，但不超过 maxBlockSize。
			 */
			fc_blksize = fc_set->nextBlockSize;
			fc_set->nextBlockSize <<= 1;
			if (fc_set->nextBlockSize > fc_set->maxBlockSize)
				fc_set->nextBlockSize = fc_set->maxBlockSize;

			/* 我们还需要一个块头，因此将其添加到所需大小中 */
			fc_required_size += Generation_BLOCKHDRSZ;

			/* 将大小向上四舍五入到下一个 2 的幂 */
			if (fc_blksize < fc_required_size)
				fc_blksize = pg_nextpower2_size_t(fc_required_size);

			fc_block = (GenerationBlock *) malloc(fc_blksize);

			if (fc_block == NULL)
				return NULL;

			fc_context->mem_allocated += fc_blksize;

			/* 初始化新块 */
			fc_GenerationBlockInit(fc_block, fc_blksize);

			/* 将其添加到双向链表块中 */
			dlist_push_head(&fc_set->blocks, &fc_block->node);

			/* 将 freeblock 清零，以防它已满 */
			fc_set->freeblock = NULL;
		}

		/* 同时将其用作当前分配块 */
		fc_set->block = fc_block;
	}

	/* 我们现在应该有一个足够的空闲空间的块 */
	Assert(fc_block != NULL);
	Assert((fc_block->endptr - fc_block->freeptr) >= Generation_CHUNKHDRSZ + fc_chunk_size);

	fc_chunk = (GenerationChunk *) fc_block->freeptr;

	/* 准备初始化块头。 */
	VALGRIND_MAKE_MEM_UNDEFINED(fc_chunk, Generation_CHUNKHDRSZ);

	fc_block->nchunks += 1;
	fc_block->freeptr += (Generation_CHUNKHDRSZ + fc_chunk_size);

	Assert(fc_block->freeptr <= fc_block->endptr);

	fc_chunk->block = fc_block;
	fc_chunk->context = fc_set;
	fc_chunk->size = fc_chunk_size;

#ifdef MEMORY_CONTEXT_CHECKING
	fc_chunk->requested_size = fc_size;
	/* 设置标记以捕捉“未使用”空间的覆盖 */
	if (fc_size < fc_chunk->size)
		set_sentinel(GenerationChunkGetPointer(fc_chunk), fc_size);
#endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY
	/* 用垃圾填充分配的空间 */
	randomize_mem((char *) GenerationChunkGetPointer(fc_chunk), fc_size);
#endif

	/* 确保任何填充字节标记为NOACCESS。 */
	VALGRIND_MAKE_MEM_NOACCESS((char *) GenerationChunkGetPointer(fc_chunk) + fc_size,
							   chunk_size - fc_size);

	/* 禁止外部访问块头的私有部分。 */
	VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);

	return GenerationChunkGetPointer(fc_chunk);
}

/*
 * GenerationBlockInit
 *		初始化 'block'，假设 'blksize'。不会更新上下文的
 *		mem_allocated 字段。
 */
static inline void fc_GenerationBlockInit(GenerationBlock *fc_block, Size fc_blksize)
{
	fc_block->blksize = fc_blksize;
	fc_block->nchunks = 0;
	fc_block->nfree = 0;

	fc_block->freeptr = ((char *) fc_block) + Generation_BLOCKHDRSZ;
	fc_block->endptr = ((char *) fc_block) + fc_blksize;

	/* 将未分配的空间标记为 NOACCESS。 */
	VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
							   blksize - Generation_BLOCKHDRSZ);
}

/*
 * GenerationBlockIsEmpty
 *		仅当 'block' 不包含任何块时返回 true
 */
static inline bool fc_GenerationBlockIsEmpty(GenerationBlock *fc_block)
{
	return (fc_block->nchunks == 0);
}

/*
 * GenerationBlockMarkEmpty
 *		将块标记为 empty。不会释放该块。
 */
static inline void fc_GenerationBlockMarkEmpty(GenerationBlock *fc_block)
{
#if defined(USE_VALGRIND) || defined(CLOBBER_FREED_MEMORY)
	char	   *fc_datastart = ((char *) fc_block) + Generation_BLOCKHDRSZ;
#endif

#ifdef CLOBBER_FREED_MEMORY
	wipe_mem(fc_datastart, fc_block->freeptr - fc_datastart);
#else
	/* wipe_mem()本来会做这个 */
	VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
#endif

	/* 重置块，但不将其返回给malloc */
	fc_block->nchunks = 0;
	fc_block->nfree = 0;
	fc_block->freeptr = ((char *) fc_block) + Generation_BLOCKHDRSZ;
}

/*
 * GenerationBlockFreeBytes
 *		返回 'block' 中空闲的字节数
 */
static inline Size
fc_GenerationBlockFreeBytes(GenerationBlock *fc_block)
{
	return (fc_block->endptr - fc_block->freeptr);
}

/*
 * GenerationBlockFree
 *		从 'set' 中移除 'block' 并释放其占用的内存。
 */
static inline void fc_GenerationBlockFree(GenerationContext *fc_set, GenerationBlock *fc_block)
{
	/* 确保没有人尝试释放 keeper 块 */
	Assert(fc_block != fc_set->keeper);
	/* 我们也不应该释放 freeblock */
	Assert(fc_block != fc_set->freeblock);

	/* 从块列表中释放该块 */
	dlist_delete(&fc_block->node);

	((MemoryContext) fc_set)->mem_allocated -= fc_block->blksize;

#ifdef CLOBBER_FREED_MEMORY
	wipe_mem(fc_block, fc_block->blksize);
#endif

	free(fc_block);
}

/*
 * GenerationFree
 *		更新块中的块数，如果块中的所有块
 *		现在都是空闲的，则丢弃该块。
 */
static void fc_GenerationFree(MemoryContext fc_context, void *fc_pointer)
{
	GenerationContext *fc_set = (GenerationContext *) fc_context;
	GenerationChunk *fc_chunk = GenerationPointerGetChunk(fc_pointer);
	GenerationBlock *fc_block;

	/* 允许访问块头的私有部分。 */
	VALGRIND_MAKE_MEM_DEFINED(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);

	fc_block = fc_chunk->block;

#ifdef MEMORY_CONTEXT_CHECKING
	/* 测试是否有人在块的未使用空间上乱写 */
	if (fc_chunk->requested_size < fc_chunk->size)
		if (!sentinel_ok(fc_pointer, fc_chunk->requested_size))
			elog(WARNING, "detected write past chunk end in %s %p",
				 ((MemoryContext) fc_set)->name, fc_chunk);
#endif

#ifdef CLOBBER_FREED_MEMORY
	wipe_mem(fc_pointer, fc_chunk->size);
#endif

	/* 在已释放的块中将上下文重置为 NULL */
	fc_chunk->context = NULL;

#ifdef MEMORY_CONTEXT_CHECKING
	/* 在已释放的块中将 requested_size 重置为 0 */
	fc_chunk->requested_size = 0;
#endif

	fc_block->nfree += 1;

	Assert(fc_block->nchunks > 0);
	Assert(fc_block->nfree <= fc_block->nchunks);

	/* 如果块中仍有已分配的块，我们就完成了。 */
	if (fc_block->nfree < fc_block->nchunks)
		return;

	/* 不要尝试释放 keeper 块，只需将其标记为 empty */
	if (fc_block == fc_set->keeper)
	{
		fc_GenerationBlockMarkEmpty(fc_block);
		return;
	}

	/*
	 * 如果没有 freeblock 集，或者如果这是 freeblock，则不
	 * 释放此内存，而是保留它，以便新的分配可以选择回收它。
	 */
	if (fc_set->freeblock == NULL || fc_set->freeblock == fc_block)
	{
		/* XXX 我们是否只应该回收 maxBlockSize 大小的块？ */
		fc_set->freeblock = fc_block;
		fc_GenerationBlockMarkEmpty(fc_block);
		return;
	}

	/* 还要确保该块未标记为当前块。 */
	if (fc_set->block == fc_block)
		fc_set->block = NULL;

	/*
	 * 块是空的，所以让我们把它处理掉。首先将其从
	 * 块列表中移除，然后将其返回给 malloc()。
	 */
	dlist_delete(&fc_block->node);

	fc_context->mem_allocated -= fc_block->blksize;
	free(fc_block);
}

/*
 * GenerationRealloc
 *		处理 repalloc 时，我们简单地分配一个新块，复制数据
 *		并丢弃旧块。唯一的例外是当新大小适合
 *		旧块时 - 在这种情况下，我们只需更新块头。
 */
static void * fc_GenerationRealloc(MemoryContext fc_context, void *fc_pointer, Size fc_size)
{
	GenerationContext *fc_set = (GenerationContext *) fc_context;
	GenerationChunk *fc_chunk = GenerationPointerGetChunk(fc_pointer);
	GenerationPointer fc_newPointer;
	Size		fc_oldsize;

	/* 允许访问块头的私有部分。 */
	VALGRIND_MAKE_MEM_DEFINED(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);

	fc_oldsize = fc_chunk->size;

#ifdef MEMORY_CONTEXT_CHECKING
	/* 测试是否有人在块的未使用空间上乱写 */
	if (fc_chunk->requested_size < fc_oldsize)
		if (!sentinel_ok(fc_pointer, fc_chunk->requested_size))
			elog(WARNING, "detected write past chunk end in %s %p",
				 ((MemoryContext) fc_set)->name, fc_chunk);
#endif

	/*
	 * 分配区域可能已经 >= 新大小。(特别是，
	 * 如果请求的大小是减少，我们总是会在这里退出。)
	 *
	 * 此内存上下文不使用 2 的幂块大小，而是
	 * 将块切割得尽可能小，因此大多数 repalloc() 调用
	 * 最终将在 palloc/memcpy/pfree 分支中。
	 *
	 * XXX 我们是否应该用 unlikely() 注解此条件？
	 */
	if (fc_oldsize >= fc_size)
	{
#ifdef MEMORY_CONTEXT_CHECKING
		Size		fc_oldrequest = fc_chunk->requested_size;

#ifdef RANDOMIZE_ALLOCATED_MEMORY
		/* 只有在知道先前请求的情况下才能填充额外的空间 */
		if (fc_size > fc_oldrequest)
			randomize_mem((char *) fc_pointer + fc_oldrequest,
						  fc_size - fc_oldrequest);
#endif

		fc_chunk->requested_size = fc_size;

		/*
		 * 如果这是一个增加，标记任何新可用部分为 UNDEFINED。
		 * 否则，标记过时部分为 NOACCESS。
		 */
		if (fc_size > fc_oldrequest)
			VALGRIND_MAKE_MEM_UNDEFINED((char *) fc_pointer + fc_oldrequest,
										fc_size - fc_oldrequest);
		else
			VALGRIND_MAKE_MEM_NOACCESS((char *) fc_pointer + fc_size,
									   fc_oldsize - fc_size);

		/* 设置标记以捕捉“未使用”空间的覆盖 */
		if (fc_size < fc_oldsize)
			set_sentinel(fc_pointer, fc_size);
#else							/* !MEMORY_CONTEXT_CHECKING */

		/*
		 * 我们没有信息来确定是增长旧请求还是缩小它，因此
		 * 我们保守地将整个新分配标记为 DEFINED。
		 */
		VALGRIND_MAKE_MEM_NOACCESS(fc_pointer, fc_oldsize);
		VALGRIND_MAKE_MEM_DEFINED(fc_pointer, fc_size);
#endif

		/* 禁止外部访问块头的私有部分。 */
		VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);

		return fc_pointer;
	}

	/* 分配新块 */
	fc_newPointer = fc_GenerationAlloc((MemoryContext) fc_set, fc_size);

	/* 如果请求未完成则立即返回 */
	if (fc_newPointer == NULL)
	{
		/* 禁止外部访问块头的私有部分。 */
		VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);
		return NULL;
	}

	/*
	 * GenerationAlloc() 可能已返回一个仍然 NOACCESS 的区域。
	 * 暂时将其更改为 UNDEFINED；memcpy() 将随后将
	 * 定义性从旧分配传递到新分配。如果我们知道旧
	 * 内存分配，只复制那么多。否则，使整个旧块
	 * 定义，以避免在复制当前 NOACCESS 尾部字节时出现错误。
	 */
	VALGRIND_MAKE_MEM_UNDEFINED(newPointer, fc_size);
#ifdef MEMORY_CONTEXT_CHECKING
	fc_oldsize = fc_chunk->requested_size;
#else
	VALGRIND_MAKE_MEM_DEFINED(fc_pointer, fc_oldsize);
#endif

	/* 转移现有数据（一定能够适应） */
	memcpy(fc_newPointer, fc_pointer, fc_oldsize);

	/* 释放旧块 */
	fc_GenerationFree((MemoryContext) fc_set, fc_pointer);

	return fc_newPointer;
}

/*
 * GenerationGetChunkSpace
 *		给定当前分配的块，确定它占用的总空间
 *		（包括所有内存分配开销）。
 */
static Size fc_GenerationGetChunkSpace(MemoryContext fc_context, void *fc_pointer)
{
	GenerationChunk *fc_chunk = GenerationPointerGetChunk(fc_pointer);
	Size		fc_result;

	VALGRIND_MAKE_MEM_DEFINED(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);
	fc_result = fc_chunk->size + Generation_CHUNKHDRSZ;
	VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);
	return fc_result;
}

/*
 * GenerationIsEmpty
 *		GenerationContext 是否为空，未分配任何空间？
 */
static bool fc_GenerationIsEmpty(MemoryContext fc_context)
{
	GenerationContext *fc_set = (GenerationContext *) fc_context;
	dlist_iter	fc_iter;

	dlist_foreach(fc_iter, &fc_set->blocks)
	{
		GenerationBlock *fc_block = dlist_container(GenerationBlock, node, fc_iter.cur);

		if (fc_block->nchunks > 0)
			return false;
	}

	return true;
}

/*
 * GenerationStats
 *		计算有关 Generation 上下文的内存消耗的统计信息。
 *
 * printfunc: 如果不为 NULL，将人类可读的统计字符串传递给它。
 * passthru: 将此指针传递给 printfunc。
 * totals: 如果不为 NULL，将有关该上下文的统计信息添加到 *totals 中。
 * print_to_stderr: 如果为 true 则将统计信息打印到 stderr，否则打印到 elog。
 *
 * XXX free空间仅计算块末尾的空闲空间，而不
 * 是已释放块的空间（这是未知的）。
 */
static void fc_GenerationStats(MemoryContext fc_context,
				MemoryStatsPrintFunc fc_printfunc, void *fc_passthru,
				MemoryContextCounters *fc_totals, bool fc_print_to_stderr)
{
	GenerationContext *fc_set = (GenerationContext *) fc_context;
	Size		fc_nblocks = 0;
	Size		fc_nchunks = 0;
	Size		fc_nfreechunks = 0;
	Size		fc_totalspace;
	Size		fc_freespace = 0;
	dlist_iter	fc_iter;

	/* 将上下文头包含在 totalspace 中 */
	fc_totalspace = MAXALIGN(sizeof(GenerationContext));

	dlist_foreach(fc_iter, &fc_set->blocks)
	{
		GenerationBlock *fc_block = dlist_container(GenerationBlock, node, fc_iter.cur);

		fc_nblocks++;
		fc_nchunks += fc_block->nchunks;
		fc_nfreechunks += fc_block->nfree;
		fc_totalspace += fc_block->blksize;
		fc_freespace += (fc_block->endptr - fc_block->freeptr);
	}

	if (fc_printfunc)
	{
		char		fc_stats_string[200];

		snprintf(fc_stats_string, sizeof(fc_stats_string),
				 "%zu total in %zu blocks (%zu chunks); %zu free (%zu chunks); %zu used",
				 fc_totalspace, fc_nblocks, fc_nchunks, fc_freespace,
				 fc_nfreechunks, fc_totalspace - fc_freespace);
		fc_printfunc(fc_context, fc_passthru, fc_stats_string, fc_print_to_stderr);
	}

	if (fc_totals)
	{
		fc_totals->nblocks += fc_nblocks;
		fc_totals->freechunks += fc_nfreechunks;
		fc_totals->totalspace += fc_totalspace;
		fc_totals->freespace += fc_freespace;
	}
}


#ifdef MEMORY_CONTEXT_CHECKING

/*
 * GenerationCheck
 *		遍历块并检查内存的一致性。
 *
 * 注意：报告错误为警告，而不是错误或致命错误。否则，当出现问题时，您会发现自己陷入无限循环，因为在elog清理尝试释放内存时将再次进入此例程！
 */
static void fc_GenerationCheck(MemoryContext fc_context)
{
	GenerationContext *fc_gen = (GenerationContext *) fc_context;
	const char *fc_name = fc_context->name;
	dlist_iter	fc_iter;
	Size		fc_total_allocated = 0;

	/* 遍历此上下文中的所有块 */
	dlist_foreach(fc_iter, &fc_gen->blocks)
	{
		GenerationBlock *fc_block = dlist_container(GenerationBlock, node, fc_iter.cur);
		int			fc_nfree,
					fc_nchunks;
		char	   *fc_ptr;

		fc_total_allocated += fc_block->blksize;

		/*
		 * nfree > nchunks 肯定是错误的。相等是被允许的，因为块
		 * 可能完全为空，如果它是空闲块。
		 */
		if (fc_block->nfree > fc_block->nchunks)
			elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
				 fc_name, fc_block->nfree, fc_block, fc_block->nchunks);

		/* 现在遍历块并计数。 */
		fc_nfree = 0;
		fc_nchunks = 0;
		fc_ptr = ((char *) fc_block) + Generation_BLOCKHDRSZ;

		while (fc_ptr < fc_block->freeptr)
		{
			GenerationChunk *fc_chunk = (GenerationChunk *) fc_ptr;

			/* 允许访问块头的私有部分。 */
			VALGRIND_MAKE_MEM_DEFINED(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);

			/* 移动到下一个块 */
			fc_ptr += (fc_chunk->size + Generation_CHUNKHDRSZ);

			fc_nchunks += 1;

			/* 块有块指针和上下文指针，因此检查两者 */
			if (fc_chunk->block != fc_block)
				elog(WARNING, "problem in Generation %s: bogus block link in block %p, chunk %p",
					 fc_name, fc_block, fc_chunk);

			/*
			 * 检查有效的上下文指针。注意这是一个不完整的
			 * 测试，因为 palloc(0) 产生一个分配的块，其
			 * requested_size == 0。
			 */
			if ((fc_chunk->requested_size > 0 && fc_chunk->context != fc_gen) ||
				(fc_chunk->context != fc_gen && fc_chunk->context != NULL))
				elog(WARNING, "problem in Generation %s: bogus context link in block %p, chunk %p",
					 fc_name, fc_block, fc_chunk);

			/* 现在确保块大小正确 */
			if (fc_chunk->size < fc_chunk->requested_size ||
				fc_chunk->size != MAXALIGN(fc_chunk->size))
				elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p",
					 fc_name, fc_block, fc_chunk);

			/* 块是否已分配？ */
			if (fc_chunk->context != NULL)
			{
				/* 检查哨兵，但仅在已分配的块中 */
				if (fc_chunk->requested_size < fc_chunk->size &&
					!sentinel_ok(fc_chunk, Generation_CHUNKHDRSZ + fc_chunk->requested_size))
					elog(WARNING, "problem in Generation %s: detected write past chunk end in block %p, chunk %p",
						 fc_name, fc_block, fc_chunk);
			}
			else
				fc_nfree += 1;

			/*
			 * 如果块已分配，禁止外部访问块头的私有部分。
			 */
			if (fc_chunk->context != NULL)
				VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, GENERATIONCHUNK_PRIVATE_LEN);
		}

		/*
		 * 确保我们得到了预期数量的已分配块和空闲块
		 * （在块头中跟踪）。
		 */
		if (fc_nchunks != fc_block->nchunks)
			elog(WARNING, "problem in Generation %s: number of allocated chunks %d in block %p does not match header %d",
				 fc_name, fc_nchunks, fc_block, fc_block->nchunks);

		if (fc_nfree != fc_block->nfree)
			elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d",
				 fc_name, fc_nfree, fc_block, fc_block->nfree);
	}

	Assert(fc_total_allocated == fc_context->mem_allocated);
}

#endif							/* MEMORY_CONTEXT_CHECKING */
